﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Globalization;
namespace gemGenerator
{
    public partial class Form1 : Form
    {
        const double PerspectiveDepth = 2.5;
        const double rp = 1 / (1.3 * PerspectiveDepth);

        //if dot of two normals is higher, they are considered parallel 
        //set to higher value to visualize girdle "cogs"
        const double MinPlaneDot = 0.999; 
        int RefreshCount = 0;
        BindingList<VertexRing> tiers = new BindingList<VertexRing>();
        private bool TryParse(string str, out double val, double limit = double.PositiveInfinity)
        {
            bool ret = double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out val);
            return (ret && Math.Abs(val) < limit);
        }
        public Form1()
        {
            InitializeComponent();
            listBox1.DataSource = tiers;
            tbDiameter.DataBindings.Add(new Binding("Text", tiers, "Diameter"));
            tbHeight.DataBindings.Add(new Binding("Text", tiers, "Height"));
            tbLayout.DataBindings.Add(new Binding("Text", tiers, "Layout")); 
            
            //on startup show brilliant cut
            tiers.Add(new VertexRing(-0.862, 0, "1"));
            tiers.Add(new VertexRing(-0.647, 0.28, "8x2"));
            tiers.Add(new VertexRing(0, 1.02, "16x1"));
            tiers.Add(new VertexRing(0.162, 0.863, ">1,8x2"));
            tiers.Add(new VertexRing(0.324, 0.57367, "8x1"));
            
            pictureBox1.Image = new Bitmap(640,640);
            comboBox1.SelectedIndex = 0;
        }
        int ScaleUnit = 150;
        double bound = 1.0;
        void ResetImage()
        {
            Graphics.FromImage(pictureBox1.Image).Clear(Color.White);
        }
        Matrix3 ViewTransform = Matrix3.RotationX(+Math.PI);
        void DrawFaceTransformed(VectorD aa, VectorD bb, VectorD cc)
        { //drawing in free look mode
            int scale = (int)(2 * ScaleUnit/bound);
            int center = 2 * ScaleUnit;
            VectorD a = ViewTransform * aa;
            VectorD b = ViewTransform * bb;
            VectorD c = ViewTransform * cc;
            
            VectorD normal = Op.Normal(a, b, c);
           
            int cos = (int)(128 + 127 * Op.Dot(Op.Normalize(new VectorD(-1, -1, -0.5)), normal));
            if (cos < 0) cos = 0;
            if (cos > 255) cos = 255;

            double p = PerspectiveDepth;
            a.X /= rp * (a.Z + bound + p);
            a.Y /= rp * (a.Z + bound + p);
            b.X /= rp * (b.Z + bound + p);
            b.Y /= rp * (b.Z + bound + p);
            c.X /= rp * (c.Z + bound + p);
            c.Y /= rp * (c.Z + bound + p);
            normal = Op.Cross(b - a, c - a);
            if (normal.Z < 0)
            {//is facing me
                Point[] tri = { 
                    new Point((int)(a.X * scale + center), (int)(a.Y * scale + center)),
                    new Point((int)(b.X * scale + center), (int)(b.Y * scale + center)),
                    new Point((int)(c.X * scale + center), (int)(c.Y * scale + center))};
                Graphics.FromImage(pictureBox1.Image).FillPolygon(new SolidBrush(Color.FromArgb(cos, cos, cos)), tri);
            }
            
        }
        void DrawFace(VectorD a, VectorD b, VectorD c)
        { // in normal mode
            int scale = (int)( ScaleUnit / bound);
            int center = ScaleUnit;
            VectorD normal = Op.Normal(a,b,c);
            //top, bottom
            double cosd = (128 + 127 * Op.Dot(Op.Normalize(new VectorD(-3, -5, 4)), normal));
            int cos = (int)cosd;
            if (cbContrast.Checked)
                cos = (int)(314 * cosd) % 256;
            if (normal.Z>=0)
            {//is in crown
                Point[] tri = { 
                    new Point((int)(a.X * scale+ center), (int)(a.Y * scale+ center)),
                    new Point((int)(b.X * scale+ center), (int)(b.Y * scale+ center)),
                    new Point((int)(c.X * scale+ center), (int)(c.Y * scale+ center))};
                Graphics.FromImage(pictureBox1.Image).FillPolygon(new SolidBrush(Color.FromArgb(cos, cos, cos)), tri);
            }
            else 
            { //is in pavillion
                Point[] tri = { 
                    new Point((int)(a.X * scale + 3*center), (int)(a.Y * scale + center)),
                    new Point((int)(b.X * scale + 3*center), (int)(b.Y * scale + center)),
                    new Point((int)(c.X * scale + 3*center), (int)(c.Y * scale + center))
                };
                Graphics.FromImage(pictureBox1.Image).FillPolygon(new SolidBrush(Color.FromArgb(255 - cos, 255 - cos, 255 - cos)), tri);
            }

           
            //left,front
            cos = (int)(128 + 127 * Op.Dot(Op.Normalize(new VectorD(-1, 0, 1)), Op.Normalize( normal)));
            if (normal.Y>=0)
            {//is in front
                Point[] tri = { 
                    new Point((int)(a.X * scale+ center), (int)(-a.Z * scale+ 3*center)),
                    new Point((int)(b.X * scale+ center), (int)(-b.Z * scale+ 3*center)),
                    new Point((int)(c.X * scale+ center), (int)(-c.Z * scale+ 3*center))};
                Graphics.FromImage(pictureBox1.Image).FillPolygon(new SolidBrush(Color.FromArgb(cos, cos, cos)), tri);
            }

            cos = (int)(128 + 127 * Op.Dot(Op.Normalize(new VectorD(-0, 1, -1)), Op.Normalize(normal)));
            
            if (normal.X >= 0)
            { //is in left
                Point[] tri = { 
                    new Point((int)(a.Y * scale+ 3*center), (int)(-a.Z * scale+ 3*center)),
                    new Point((int)(b.Y * scale+ 3*center), (int)(-b.Z * scale+ 3*center)),
                    new Point((int)(c.Y * scale+ 3*center), (int)(-c.Z * scale+ 3*center))};
                Graphics.FromImage(pictureBox1.Image).FillPolygon(new SolidBrush(Color.FromArgb(256 - cos, 256 - cos, 256 - cos)), tri);
            }
            
        }

        void DrawEdge(VectorD a, VectorD b, VectorD n1, VectorD n2) //vertices + normals of adjacent triangles
        {
            if (!cbTriEdges.Checked && Op.Dot(n1, n2) > MinPlaneDot)
                return; //triangles are in one plane

            int scale = (int)(ScaleUnit / bound);
            int center = ScaleUnit;
            Graphics g = Graphics.FromImage(pictureBox1.Image);
           

            if (n1.Z >= 0 || n2.Z >=0)
            {//is in crown
                g.DrawLine(Pens.Black, 
                    (int)(a.X * scale+ center), (int)(a.Y * scale+ center),
                    (int)(b.X * scale+ center), (int)(b.Y * scale+ center));
            }
            if (n1.Z <= 0 || n2.Z <= 0)
            { //is in pavillion
                g.DrawLine(Pens.Black, 
                    (int)(a.X * scale + 3*center), (int)(a.Y * scale + center),
                    (int)(b.X * scale + 3*center), (int)(b.Y * scale + center));    
            }

            //left,front
            if (n1.Y >= 0 || n2.Y >= 0)
            {//is in front
                g.DrawLine(Pens.Black,
                    (int)(a.X * scale + center), (int)(-a.Z * scale + 3 * center),
                    (int)(b.X * scale + center), (int)(-b.Z * scale + 3 * center));
            }

            
            if (n1.X >= 0 || n2.X >= 0)
            { //is in left
                 g.DrawLine(Pens.Black,
                    (int)(a.Y * scale+ 3*center), (int)(-a.Z * scale+ 3*center),
                    (int)(b.Y * scale+ 3*center), (int)(-b.Z * scale+ 3*center));
            }

        }

     //   VectorD right = new VectorD(1, 0, 0);
     //   VectorD up = new VectorD(0, 1, 0);

        void DrawEdgeTransformed(VectorD aa, VectorD bb, VectorD n1, VectorD n2) //vertices of edge + third vertices of adjacent triangles
        {
            if (!cbTriEdges.Checked && Op.Dot(n1, n2) > MinPlaneDot)
                return; //triangles are in one plane

            int scale = (int)(2 * ScaleUnit / bound);
            int center = 2 * ScaleUnit;
            VectorD a = ViewTransform * aa;
            VectorD b = ViewTransform * bb;

        //    VectorD dir = Op.Cross(ViewTransform * right, ViewTransform * up); //would have to be inverse transformation

            double p = PerspectiveDepth;
            a.X /= rp * (a.Z + bound + p);
            a.Y /= rp * (a.Z + bound + p);
            b.X /= rp * (b.Z + bound + p);
            b.Y /= rp * (b.Z + bound + p);


            //if (Op.Dot(n1, dir) < 0 || Op.Dot(n2, dir) < 0)//is facing me
            {
                Graphics.FromImage(pictureBox1.Image).DrawLine( Pens.Black,
                    (int)(a.X * scale + center), (int)(a.Y * scale + center),
                    (int)(b.X * scale + center), (int)(b.Y * scale + center));   
            }
            

        }
        CoordinateTransform tr = new PolarTransform();
     
        void Shuffle<T>(IList<T> list)
        { //it does speed up the convex hull building a lot
            Random r = new Random();
            T buf;
            for (int i = 0; i < list.Count; ++i)
            {
                int j = r.Next(list.Count);
                buf = list[i];
                list[i] = list[j];
                list[j] = buf;
            }
        }
        IList<VectorD> vcs = null;
        LinkedList<Triangle> tris = null;
        IDictionary<EdgeKey, EdgeWings> edges = null;
        private void CreateGem()
        {
            if (CeaseFire) return;
            LinkedList<VectorD> vertices = new LinkedList<VectorD>();
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            foreach (VertexRing vr in tiers)
            {
                if (vr.CreateVertices(vertices, tr) == false) return;
            }
            sw.Stop();
            Text = "Parse: "+sw.ElapsedMilliseconds.ToString() + "ms";

          
           // RemoveCloseVertices(vertices);
            vcs = new List<VectorD>(vertices);
          
            sw.Restart();
            Shuffle(vcs);
            tris = ConvexHullBuilder.GetHull(vcs, out edges);
            sw.Stop();
            Text += " + Hull: " + sw.ElapsedMilliseconds.ToString() + "ms";

            if (tris == null) return; //no dice

            double trimr;
            int trimn;
            if (checkBox2.Checked && TryParse(tbTrim.Text, out trimr, 5.0) && int.TryParse(tbTrimNum.Text, out trimn))
            {
                sw.Restart();
                vcs = EdgeRounder.MakeGirdle(vcs, tris, tr, trimr, trimn);
                sw.Stop();
                Text += " + Girdle: " + sw.ElapsedMilliseconds.ToString() + "ms";

                sw.Restart();
                Shuffle(vcs);
                tris = ConvexHullBuilder.GetHull(vcs, out edges);
                sw.Stop();
                Text += " + Hull 2: " + sw.ElapsedMilliseconds.ToString() + "ms";

            }
            if (tris == null) return;
            bound = 0;
            foreach (var v in vcs)
            {
                double L = Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z);
                if (L > bound) bound = L;
            }
            Text += ", total num: " + (++RefreshCount).ToString();
            RedrawGem();
        }
        private void button1_Click(object sender, EventArgs e)
        {//save

            CreateGem();
           
            pictureBox1.Refresh();
            
            if (sender != null) //called by button
            {
                //overwrite existing file?
                if(System.IO.File.Exists(textBox1.Text) 
                    && MessageBox.Show("File already exists. Overwrite?", "Save", MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.No)
                        return;

                StreamWriter obj = new StreamWriter(textBox1.Text);
                obj.WriteLine("# " + vcs.Count + " vertices, " + tris.Count + " triangles.");
                foreach (VertexRing v in tiers)
                {
                    obj.WriteLine( "#ring "+ v.Coordinates);
                }
                if (checkBox2.Checked)
                {
                    obj.WriteLine("#girdle " + tbTrim.Text +" "+ tbTrimNum.Text); 
                }
                obj.WriteLine("#transform "
                    + comboBox1.SelectedIndex 
                    + " " + tbScaleX.Text
                    + " " + tbScaleY.Text
                    + " " + tbParX.Text
                    + " " + tbParY.Text);
                
                foreach (VectorD v in vcs)
                    obj.WriteLine(String.Format("v {0:0.0000} {1:0.0000} {2:0.0000}", v.X, v.Y, v.Z));
                foreach (Triangle t in tris)
                {
                    obj.WriteLine("f " + (1 + t.V1).ToString() + " " + (1 + t.V2).ToString() + " " + (1 + t.V3).ToString());
                }
                obj.Close();
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {//add ring
            VertexRing v=null;
            
            if(listBox1.SelectedItem!=null) 
                v = (listBox1.SelectedItem as VertexRing).Clone() as VertexRing;
            else v = new VertexRing(0, 1, "8x(1)");
            tiers.Add(v);
            listBox1.SelectedIndex = listBox1.Items.Count-1; //select the new one
            CreateGem();
        }

        private void button3_Click(object sender, EventArgs e)
        {//delete ring
            int sel = listBox1.SelectedIndex; 
            if (listBox1.SelectedIndex >= 0)
                tiers.RemoveAt(listBox1.SelectedIndex);
            if(listBox1.Items.Count>0)
                listBox1.SelectedIndex = listBox1.Items.Count > sel ? sel : listBox1.Items.Count - 1;
            CreateGem();
        }

        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            int i = listBox1.SelectedIndex ;
            if (i == -1)
            {
                tbDiameter.Enabled = false;
                tbHeight.Enabled = false;
                tbLayout.Enabled = false;
            }
            else
            {
                tbDiameter.Enabled = true;
                tbHeight.Enabled = true;
                tbLayout.Enabled = true;
            }
        }

        private void ParamChanged(object sender, EventArgs e)
        {
          //  MessageBox.Show(sender.ToString());
            TextBox t = sender as TextBox;
            if (!t.Focused) return; //changed by listbox
            int ind = listBox1.SelectedIndex;
            
            VertexRing v = listBox1.SelectedItem as VertexRing;
           
            if (v != null)
            {
                if (t == tbLayout)
                    v.Layout = t.Text;
                else if(t==tbDiameter)
                    v.Diameter =t.Text;
                else 
                    if(t == tbHeight)
                        v.Height = t.Text;
                CreateGem();
            }
        }

        private void bindingSource1_CurrentChanged(object sender, EventArgs e)
        {

        }

        private void checkBox2_CheckedChanged(object sender, EventArgs e)
        {
            CreateGem();
        }

        private void textBox2_TextChanged(object sender, EventArgs e)
        {
            if (checkBox2.Checked) CreateGem();
        }

        private void tbTrim_TextChanged(object sender, EventArgs e)
        {
            if (checkBox2.Checked) CreateGem();
        }

        private void WriteProperty(object sender, EventArgs e)
        {
            if(listBox1.SelectedIndex>=0)
                tiers.ResetItem(listBox1.SelectedIndex);
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            switch (comboBox1.SelectedIndex)
            {
                case 0: tr = new PolarTransform();
                    break;
                case 1: tr = new CushionTransform();
                    break;
                case 2: tr = new RectangleTransform();
                    break;
                case 3: tr = new PearTransform();
                    break;
                case 4: tr = new MarquiseTransform();
                    break;
            }
            ScaleChanged(null, null);
            TransformParamChanged(null, null);
            if (checkBox1.Checked) CreateGem();
        }

        private void ScaleChanged(object sender, EventArgs e)
        {
            double cx, cy;
            if (TryParse(tbScaleX.Text, out cx, 10.0) && TryParse(tbScaleY.Text, out cy, 10.0) && tr.SetScale(cx, cy) && checkBox1.Checked)
                CreateGem();
        }

        private void TransformParamChanged(object sender, EventArgs e)
        {
            double cx, cy;
            if (TryParse(tbParX.Text, out cx, 50.0) && TryParse(tbParY.Text, out cy, 50.0) && tr.SetParam(cx, cy) && checkBox1.Checked)
                CreateGem();
        }

        bool CeaseFire = false;
        private void button4_Click(object sender, EventArgs e)
        { //load
            if (!File.Exists(textBox1.Text))
            {
                MessageBox.Show("File doesn't exist.");
                return;
            }
            CeaseFire = true;
            StreamReader obj = new StreamReader(textBox1.Text);
            tiers.Clear();
            checkBox2.Checked = false;
            try
            {
                while (!obj.EndOfStream)
                {
                    string line = obj.ReadLine().Replace(' ', '\n');
                    StringReader srl = new StringReader(line);
                    string atom = srl.ReadLine();
                    if (atom == "#ring")
                    {
                        VertexRing vr = new VertexRing(0, 0, "");
                        vr.Height = srl.ReadLine();
                        vr.Diameter = srl.ReadLine();
                        vr.Layout = srl.ReadLine();
                        tiers.Add(vr);
                    }
                    else if (atom == "#girdle" || atom == "#round") //preferred is "girdle"
                    {
                        checkBox2.Checked = true;
                        tbTrim.Text = srl.ReadLine();
                        tbTrimNum.Text = srl.ReadLine();
                    }
                    else if (atom == "#transform")
                    {
                        comboBox1.SelectedIndex = int.Parse(srl.ReadLine());
                        tbScaleX.Text = srl.ReadLine();
                        tbScaleY.Text = srl.ReadLine();
                        tbParX.Text = srl.ReadLine();
                        tbParY.Text = srl.ReadLine();
                        ScaleChanged(null, null);
                        TransformParamChanged(null, null);
                    }
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message, "File parsing error", MessageBoxButtons.OK);
            }
            obj.Close();
            tiers.ResetBindings();
            CeaseFire = false;

            if (listBox1.Items.Count > 0)
            {
                listBox1.SelectedIndex = 0;
                tbDiameter.Enabled = true;
                tbHeight.Enabled = true;
                tbLayout.Enabled = true;
            }
            CreateGem();
        }

        void RedrawGem()
        {
            bool transformed = checkBox3.Checked;
            if (vcs == null || tris == null) return;
            int size = Math.Min(pictureBox1.Width, pictureBox1.Height);
            ScaleUnit =  size/ 4;
            if (pictureBox1.Image.Width < size)
                pictureBox1.Image = new Bitmap(size, size);
            ResetImage();
            foreach (Triangle t in tris)
            {
                if(transformed) DrawFaceTransformed(vcs[t.V1], vcs[t.V2], vcs[t.V3]);
                else            DrawFace(vcs[t.V1], vcs[t.V2], vcs[t.V3]);
            }
            
            if(cbDrawEdges.Checked)
                foreach (KeyValuePair<EdgeKey, EdgeWings> p in edges)
                {
                    if (transformed) DrawEdgeTransformed(vcs[p.Key.V1], vcs[p.Key.V2], Op.Normal(vcs, p.Value.T1.Value), Op.Normal(vcs, p.Value.T2.Value));
                    else             DrawEdge           (vcs[p.Key.V1], vcs[p.Key.V2], Op.Normal(vcs, p.Value.T1.Value), Op.Normal(vcs, p.Value.T2.Value));
                }
            pictureBox1.Refresh();
        }
        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            PrevMouseX = e.X;
            PrevMouseY = e.Y;
        }

        private void checkBox3_CheckedChanged(object sender, EventArgs e)
        {
            cbContrast.Enabled = !checkBox3.Checked;
            RedrawGem();
        }

        int PrevMouseX = 0;
        int PrevMouseY = 0;
        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (!checkBox3.Checked) return;
            double sens = Math.PI / 2 / (ScaleUnit * 2);
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                ViewTransform = Matrix3.RotationY((e.X - PrevMouseX) * -sens) * ViewTransform;
                ViewTransform = Matrix3.RotationX((e.Y - PrevMouseY) * sens) * ViewTransform;
                PrevMouseX = e.X;
                PrevMouseY = e.Y;
                RedrawGem();
            }
            else if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                ViewTransform = Matrix3.RotationZ((e.X - PrevMouseX) * sens) * ViewTransform;
                PrevMouseX = e.X;
                RedrawGem();
            }
        }

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            
        }

        private void Form1_ResizeEnd(object sender, EventArgs e)
        {
            RedrawGem();
        }

        private void checkBox3_KeyPress(object sender, KeyPressEventArgs e)
        {
            const double delta = 0.05;
            switch (e.KeyChar)
            {
                case 'e':
                case 'E':
                    ViewTransform = Matrix3.RotationZ(delta) * ViewTransform;
                    RedrawGem();
                    break;
                case 'q':
                case 'Q':
                    ViewTransform = Matrix3.RotationZ(-delta) * ViewTransform;
                    RedrawGem();
                    break;
                case 'r':
                case 'R':
                    ViewTransform = Matrix3.RotationX(Math.PI);
                    RedrawGem();
                    break;
            }
        }

        private void pictureBox1_DoubleClick(object sender, EventArgs e)
        {
            ViewTransform = Matrix3.RotationX(Math.PI);
            RedrawGem();
        }
        
        FormWindowState PreviousState = FormWindowState.Normal;
        private void Form1_SizeChanged(object sender, EventArgs e)
        {
            if (WindowState == FormWindowState.Minimized) return;
            if (PreviousState != WindowState)
            {
                RedrawGem();
                PreviousState = WindowState;
            }
            
        }

        private void button5_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                textBox1.Text = openFileDialog1.FileName;
        }

        private void cbContrast_CheckedChanged(object sender, EventArgs e)
        {
            RedrawGem();
        }

        private void tabPage3_Click(object sender, EventArgs e)
        {

        }

        private void cbDrawEdges_CheckedChanged(object sender, EventArgs e)
        {
            cbTriEdges.Enabled = cbDrawEdges.Checked;
            RedrawGem();
        }

        private void cbTriEdges_CheckedChanged(object sender, EventArgs e)
        {
            RedrawGem();
        }
    }
}
